<?php
/**     
  *
  * Copyright (c) 2009, Persistent Systems Limited
  *
  * Redistribution and use, with or without modification, are permitted 
  *  provided that the following  conditions are met:
  *   - Redistributions of source code must retain the above copyright notice, 
  *     this list of conditions and the following disclaimer.
  *   - Neither the name of Persistent Systems Limited nor the names of its contributors 
  *     may be used to endorse or promote products derived from this software 
  *     without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  
/**
  *
  * This file contains the definition of Dictionary Class
  */
/**
  * The Dictionary Class.
  * 
  * This class is used to hold object-Value pair. This Dictionary
  * requires object to be derived from Object class. Which cause
  * object to have unique ObjectID.
  */
class Dictionary
{
    /**
     * This array will hold the dictionary entries as Key-Value Pair
     * Dictionary::Key => Guid
     * Dictionary::Value => Entry(user_key, user_value)
     */
    protected $_entries = array();
    
    /** 
     * To add an entry to dictionary
     * @param <object> $key The key object, Must be derived from Object
     * @param <object> $value The value object     
     */
    public function Add($key, $value)
    {		
        $objectID = $key->getObjectID();
        if(array_key_exists($objectID, $this->_entries))
        {
            throw new Exception("Key already exists");
        }
        $this->_entries[$objectID] = new Pair($key, $value);
    }
    
    /** 
     * To remove an entry from dictionary
     * @param <object> $key The key object, Must be derived from Object
     * @returns <bool> TRUE: if $key exists, FALSE: if $key not exists
     *		
     */	
    public function Remove($key)
    {
        $objectID = $key->getObjectID();		
        if(array_key_exists($objectID, $this->_entries))
        {
            unset($this->_entries[$objectID]);			
            return TRUE;
        }
        return FALSE;
    }	
    
    /**
     * To remove all entries from dictionary
     */
    public function RemoveAll()
    {
        foreach($this->_entries as $key => $value)
        {
            unset($this->_entries[$key]);
        }
    }
    
    /**
     * @param <object> $key
     * To check a particular key exists in the dictionary
     */
    public function ContainsKey($key)
    {
        $objectID = $key->getObjectID();
        if(array_key_exists($objectID, $this->_entries))
        {			
            return TRUE;
        }
        return FALSE;
    }
    
    /*
     * 
     * @Returns <Object list>
     * To retrives collection of value objects
     */
    public function Values()
    {
        $values = array();
        foreach($this->_entries as $key => $value)
        {
            $values[] = $value->entry2;
        }	
        return $values;
    }
    
    /*
     * @Returns <Object list>
     * To retrives collection of key objects
     */
    public function Keys()
    {
        $keys = array();
        foreach($this->_entries as $key => $value)
        {
            $keys[] = $value->entry1;
        }	
        return $keys;
    }
    
    /**
     * To get number of Key:Value pair in the Dictionary
     * @Return<integer>	 
     */
    public function Count()
    {
        return count($this->_entries);
    }
    
    /*
     * To retrives value corrosponding to a key object
     * @param <object> $key The key object, Must be derived from Object
     * @param <object> $value [OUT] The value object, will conatins the value corrosponding
     * to key on return
     * Returns <bool> TRUE: if $key exists,	FALSE: if $key not exists 
     */
    public function TryGetValue($key, &$value)
    {
        $objectID = $key->getObjectID();
        if(array_key_exists($objectID, $this->_entries))
        {
            $value = $this->_entries[$objectID]->entry2;
            return TRUE;			
        }		
        $value = null;
        return FALSE;
    }
    
    /* To Sort the dictionary 'values' based on any property of class representing
     * the value object.
     * @param <string> $propertyName The name of property used for sorting
     * @Returns No return value 
     */
    public function Sort($propertyName)
    {
        $sortArray = array();
        foreach($this->_entries as $key => $value)
        {
            $property = new ReflectionProperty($value->entry2, $propertyName);
            $sortArray[$key] = $property->getValue($value->entry2);
        }
        asort($sortArray);
        $newEntries = array();
        foreach($sortArray as $key => $value)
        {
            $newEntries[$key] = $this->_entries[$key];			
        }
        unset($this->_entries);
        $this->_entries = $newEntries;
    }
    
    /*
     * To get the index of a key in the dictionary
     * @param <object> $key The key whose index to be returned
     * Returns<integer> index: if $key exists, -1: if $key not exists 
     */
    public function FindEntry($key)
    {
        $index = -1;
        $objectID = $key->getObjectID();
        foreach($this->_entries as $key => $value)
        {
            $index++;
            if($key == $objectID)
            {
                return $index;
            }			
        }
        return -1;
    }
    
    /*
     * To get a value based on index
     * @param integer $index The index of value to be returned
     * Returns:
     *		valueobject: if index is with in the range
     * Will throw exception if index is out of boundary
     */
    public function GetAt($index)
    {
        if($index < 0 || $index >= count($this->_entries))
        {
            throw new Exception("index out of boundary");
        }
        $i = 0;
        foreach($this->_entries as $key => $value)
        {
            if($i == $index)
            {
                return $value->entry2;
            }		
            $i++;
        }
    }
    
    /**
     * @param <Dictionary> $dictionary1
     * @param <Dictionary> $dictionary2
     * @param <string> propertyName
     * @param <anyType> $propertyValue
     * @patam <bool> $condition
     * This function will merge two dictionaries. If $propertyName and $propertyValue  specified
     * then the merged result will only contains <key Value> pairs, where value of $propertyName
     * of each Value will be eual or not equal to $propertyValue baed on $condition
     */
    public static function Merge($dictionary1, $dictionary2, $propertyName = null, $propertyValue = null, $condition = TRUE)
    {
        $mergedDictionary = new Dictionary();
        foreach($dictionary1->_entries as $key => $value)
        {			
            if(($propertyName != null) &&
               (!Dictionary::canAdd($value->entry2, $propertyName, $propertyValue, $condition)))
            {					
                continue;
            }
            $mergedDictionary->Add($value->entry1, $value->entry2);
        }
        
        foreach($dictionary2->_entries as $key => $value)
        {
            if(($propertyName != null) &&
               (!Dictionary::canAdd($value->entry2, $propertyName, $propertyValue, $condition)))
            {					
                continue;
            }
            $mergedDictionary->Add($value->entry1, $value->entry2);
        }
        return $mergedDictionary;
    }
    
    /**
     * @param <anyType> value
     * @param <string> propertyName
     * @param <anyType> $propertyValue
     * @patam <bool> $condition
     * Tests a value can be added to merged dictionary
     */
    protected static function canAdd($value, $propertyName, $propertyValue, $condition)
    {
        $property = new ReflectionProperty($value, $propertyName);
        if((($condition == TRUE) &&
           ($property->getValue($value) != $propertyValue)) ||
           (($condition == FALSE) &&
           ($property->getValue($value) == $propertyValue)))
           {
            return FALSE;
           }
         return TRUE;
    }
}

/**
  * The Pair Class.
  * This class is used by Dictionary to store user passed 
  * key-value pair as value in the internal array 
  *
  */
class Pair
{
    public $entry1;
    public $entry2;
    
    /**
     * Constructs a pair instance
     */
    public function Pair($entry1, $entry2)
    {
        $this->entry1 = $entry1;
        $this->entry2 = $entry2;	
    }
}
?>